#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include "image.h"
#include "png.h"
#include "jpeglib.h"

#define prt(fmt, ...)                                                                       \
    do                                                                                      \
    {                                                                                       \
        fprintf(stdout, "%s:%d<%s> " fmt, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \
    } while (0)

#ifdef DEBUG
#define prt_debug prt
#else
#define prt_debug(fmt, ...)
#endif

int write_bytes_to_file(const char *filename, void *bytes, size_t bytes_len)
{
    FILE *fp = NULL;
    size_t write_len = 0;
    fp = fopen(filename, "wb+");
    if (NULL == fp)
    {
        prt("fopen %s err! errno %d.\n", filename, errno);
        return -1;
    }

    write_len = fwrite(bytes, sizeof(unsigned char), bytes_len, fp);

    prt("file: %s is write %s, bytes_len[%lu], write_len[%lu]!\n", filename, (bytes_len == write_len) ? "successed" : "failed", bytes_len, write_len);

    fclose(fp);
    fp = NULL;
    return 0;
}

int png_to_rgb(image_t *image, const char *filename)
{
    FILE *fp = NULL;
    png_structp png_ptr = NULL;
    png_infop info_ptr = NULL;
    size_t img_size = 0;
    png_byte color_type;
    png_byte r, g, b;
    png_uint_32 width = 0;
    png_uint_32 height = 0;
    png_uint_32 rowbytes = 0;
    png_uint_32 channels = 0;
    png_bytep *row_pointers = NULL;
    png_uint_32 x, y;
    png_bytep pixel_data = NULL;
    unsigned char *src = image->rgb;
    size_t k = 0;

    fp = fopen(filename, "rb");
    if (NULL == fp)
    {
        prt("fopen %s err! errno %d.\n", filename, errno);
        return -1;
    }

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (NULL == png_ptr)
    {
        prt("png_create_read_struct err! errno %d.\n", errno);
        fclose(fp);
        return -1;
    }

    info_ptr = png_create_info_struct(png_ptr);
    if (NULL == info_ptr)
    {
        prt("png_create_info_struct err! errno %d.\n", errno);
        fclose(fp);
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return -1;
    }

    if (setjmp(png_jmpbuf(png_ptr)))
    {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        fclose(fp);
        prt("png_jmpbuf setjmp is Error!\n");
        return -1;
    }

    color_type = png_get_color_type(png_ptr, info_ptr);
    if (color_type != PNG_COLOR_TYPE_RGB)
    {
        png_set_expand(png_ptr);
    }
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
    {
        png_set_expand(png_ptr);
    }

    png_init_io(png_ptr, fp);
    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);

    width = png_get_image_width(png_ptr, info_ptr);
    height = png_get_image_height(png_ptr, info_ptr);
    rowbytes = png_get_rowbytes(png_ptr, info_ptr);

    channels = rowbytes / width;
    img_size = width * height * 3;

    image->width = width;
    image->height = height;
    image->rgb_len = img_size;

    if (img_size > image->rgb_buf_len)
    {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        fclose(fp);
        prt("rgb_buf_len[%zu] < img_size[%zu]!\n", image->rgb_buf_len, img_size);
        return -1;
    }

    row_pointers = png_get_rows(png_ptr, info_ptr);
    if (NULL == row_pointers)
    {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        fclose(fp);
        prt("png_get_rows is NULL!\n");
        return -1;
    }

    prt_debug("width=%d, height=%d, rowbytes=%d, channels=%d\n", width, height, rowbytes, channels);

    for (y = 0; y < height; y++)
    {
        x = 0;
        pixel_data = row_pointers[y];
        for (x = 0; x < rowbytes; x += channels)
        {
            r = pixel_data[x + 0];
            g = pixel_data[x + 1];
            b = pixel_data[x + 2];
            // pixel_data[x + 3]; // 对于 RGBA 通道的图片才有 A 通道

            src[k + 0] = r;
            src[k + 1] = g;
            src[k + 2] = b;
            k += 3;
        }
    }
    prt_debug("k=%ld, imgsize %ld\n", k, img_size);
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    fclose(fp);
    return 0;
}

int rgb_to_jpeg(image_t *image, const char *filename)
{
    FILE *fp = NULL;
    struct jpeg_compress_struct jpeg;
    struct jpeg_error_mgr jerr;
    unsigned char *row = NULL;

    fp = fopen(filename, "wb+");
    if (NULL == fp)
    {
        prt("fopen %s err! errno %d.\n", filename, errno);
        return -1;
    }

    jpeg.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&jpeg);

    jpeg_stdio_dest(&jpeg, fp);

    jpeg.image_width = image->width;
    jpeg.image_height = image->height;
    jpeg.input_components = 3;
    jpeg.in_color_space = JCS_RGB;

    jpeg_set_defaults(&jpeg);
    jpeg_start_compress(&jpeg, TRUE); // w=0,h=0导致 此行 Empty JPEG image (DNL not supported)

    row = image->rgb;
    for (int i = 0; i < jpeg.image_height; i++)
    {
        jpeg_write_scanlines(&jpeg, &row, 1);
        row += (jpeg.image_width) * 3;
    }

    jpeg_finish_compress(&jpeg);
    jpeg_destroy_compress(&jpeg);
    fclose(fp);
    fp = NULL;
    return 0;
}

int rgb_to_jpeg_mem(image_t *image, void *buf, size_t *size)
{
    struct jpeg_compress_struct jpeg;
    struct jpeg_error_mgr jerr;
    jpeg.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&jpeg);
    jpeg_mem_dest(&jpeg, (unsigned char **)&buf, size);

    JSAMPROW row_pointer[1];

    jpeg.image_width = image->width;
    jpeg.image_height = image->height;
    jpeg.input_components = image->depth;
    jpeg.in_color_space = JCS_RGB;

    jpeg_set_defaults(&jpeg);
    jpeg_start_compress(&jpeg, TRUE);

    int row_stride = image->width * image->depth;
    while (jpeg.next_scanline < jpeg.image_height)
    {
        row_pointer[0] = (JSAMPROW)(image->rgb + jpeg.next_scanline * row_stride);
        jpeg_write_scanlines(&jpeg, row_pointer, 1);
    }

    jpeg_finish_compress(&jpeg);
    jpeg_destroy_compress(&jpeg);

    return 0;
}

int png_to_jpeg_mem(image_t *image, void *img_bytes, size_t *size, const char *filename)
{
    int ret = -1;
    ret = png_to_rgb(image, filename);
    if (ret)
    {
        prt("png_to_rgb err!\n");
        return -1;
    }

    ret = rgb_to_jpeg_mem(image, img_bytes, size);
    if (ret)
    {
        prt("png_to_rgb err! size[%zu]\n", *size);
        return -1;
    }
    return 0;
}

int test_png_to_jpeg2(const char *outname, const char *inname)
{
    size_t rgb_len = 1024 * 1024 * 6;
    int ret = -1;
    size_t size = 0;
    unsigned char *img_bytes = NULL;
    unsigned char *rgb = (unsigned char *)malloc(rgb_len);
    if (NULL == rgb)
    {
        return -1;
    }
    memset(rgb, 0, rgb_len);

    image_t image;
    memset(&image, 0, sizeof(image));

    image.rgb = rgb;
    image.rgb_buf_len = rgb_len;
    image.depth = 3;
    ret = png_to_rgb(&image, inname);
    prt_debug("--------(%u*%u) %lu---------\n", image.width, image.height, image.rgb_len);
    if (ret)
    {
        prt("png_to_rgb err!\n");
        free(rgb);
        rgb = NULL;
        return -1;
    }

    img_bytes = (unsigned char *)malloc(image.width*image.height*image.depth);

    ret = rgb_to_jpeg_mem(&image, img_bytes, &size);
    if (ret)
    {
        prt("png_to_rgb err! size[%lu]\n", size);
        free(rgb);
        rgb = NULL;
        return -1;
    }

    prt_debug("size = %lu\n", size);
    write_bytes_to_file(outname, img_bytes, size);

    free(rgb);
    rgb = NULL;

    free(img_bytes);
    img_bytes = NULL;

    return 0;
}

int test_png_to_jpeg(const char *outname, const char *inname)
{
    size_t rgb_len = 1024 * 1024 * 6;
    int ret = -1;
    unsigned char *rgb = (unsigned char *)malloc(rgb_len);
    image_t image;
    if (NULL == rgb)
    {
        return -1;
    }

    memset(rgb, 0, rgb_len);
    memset(&image, 0, sizeof(image));

    image.rgb = rgb;
    image.rgb_buf_len = rgb_len;
    ret = png_to_rgb(&image, inname);
    prt_debug("--------(%u*%u) %lu---------\n", image.width, image.height, image.rgb_len);
    if (ret)
    {
        prt("png_to_rgb err!\n");
        free(rgb);
        rgb = NULL;
        return -1;
    }

    ret = rgb_to_jpeg(&image, outname);
    if (ret)
    {
        prt("png_to_rgb err!\n");
        free(rgb);
        rgb = NULL;
        return -1;
    }

    free(rgb);
    rgb = NULL;

    return 0;
}

int read_file_magic(const char *filename, void *magic, size_t block, size_t size)
{
    FILE *fp = NULL;
    size_t ret = 0;
    fp = fopen(filename, "rb");
    if (NULL == fp)
    {
        prt("fopen %s err! errno %d.\n", filename, errno);
        return -1;
    }

    ret = fread(magic, block, size, fp);

    fclose(fp);
    fp = NULL;

    return (ret == size * block) ? 0 : -1;
}

int get_image_type(const char *filename)
{
    unsigned char magic[8] = {0};
    int ret = -1;
    const char png_magic[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
    const char jpg_magic[] = {0xFF, 0xD8};
    ret = read_file_magic(filename, magic, sizeof(magic[0]), sizeof(magic) / sizeof(magic[0]));
    if (ret != 0)
    {
        return -1;
    }

    if (0 == memcmp(magic, png_magic, sizeof(png_magic)))
    {
        return 1;
    }
    else if (0 == memcmp(magic, jpg_magic, sizeof(jpg_magic)))
    {
        return 2;
    }
    else
    {
        return -1;
    }

    return -1;
}

int is_pic_png(const char *filename)
{
    return (1 == get_image_type(filename));
}